#include "image.reader.h"

#include "stream.h"
#include "public/utils.h"
#include <tiffio.h>

#ifdef WITH_VP9
#include <vpx/vp8dx.h>
#include <vpx/vpx_decoder.h>
#include <mkvparser.hpp>
#include <string.h>
#include <omp.h>
#include <memory>
#endif

#ifdef WITH_HEVC
#include "image.reader.hevc.hh"
#endif

template<typename T> inline T swap_bytes(T v);
#ifdef BUILD_MINGW
#include <stdlib.h>
template<> inline uint16_t swap_bytes<uint16_t>(uint16_t v) {
	return _byteswap_ushort(v);
}
template<> inline uint32_t swap_bytes<uint32_t>(uint32_t v) {
	return _byteswap_ulong(v);
}
template<> inline uint64_t swap_bytes<uint64_t>(uint64_t v) {
	return _byteswap_uint64(v);
}
#else
#ifdef BUILD_APPLE
#include <libkern/OSByteOrder.h>
template<> inline uint16_t swap_bytes<uint16_t>(uint16_t v) {
	return OSSwapInt16(v);
}
template<> inline uint32_t swap_bytes<uint32_t>(uint32_t v) {
	return OSSwapInt32(v);
}
template<> inline uint64_t swap_bytes<uint64_t>(uint64_t v) {
	return OSSwapInt64(v);
}
#else
#include <byteswap.h>
template<> inline uint16_t swap_bytes<uint16_t>(uint16_t v) {
	return bswap_16(v);
}
template<> inline uint32_t swap_bytes<uint32_t>(uint32_t v) {
	return bswap_32(v);
}
template<> inline uint64_t swap_bytes<uint64_t>(uint64_t v) {
	return bswap_64(v);
}
#endif
#endif

class NrrdReader: public ImageReader {
	InputStream fs;
	bool headerOk;

	size_t dataStart;
	CubeType imgType;
	size_t sizes[3];
	size_t dim;
	bool isGzip;
	bool isLE;
	bool close() {

		return true;
	}
	~NrrdReader() {
	}
	bool getType(CubeType* type) override {
		if(!headerOk)
			return false;
		*type=imgType;
		return true;
	}
	bool getSizes(int64_t* w, int64_t* h, int64_t* d) override {
		if(!headerOk)
			return false;
		if(dim>0)
			*w=sizes[0];
		else
			*w=1;
		if(dim>1)
			*h=sizes[1];
		else
			*h=1;
		if(dim>2)
			*d=sizes[2];
		else
			*d=1;
		return true;
	}
	bool readData(char* ptr, int32_t x0, int32_t y0, int32_t z0, int64_t ystride, int64_t zstride) override {
		if(!headerOk)
			return false;
		if(!fs.seekg(dataStart))
			return false;
		if(isGzip) {
			if(!fs.pushGzip())
				return false;
		}

		auto bpv=bytes_per_voxel(imgType);
		ptr+=x0*bpv+y0*ystride+z0*zstride;
		for(size_t z=0; z<sizes[2]; z++) {
			for(size_t y=0; y<sizes[1]; y++) {
				if(!fs.read(ptr+y*ystride+z*zstride, sizes[0]*bpv))
					return false;
			}
		}

		if(isLE!=isLittleEndian()) {
			if(bpv==2) {
				swapBytes<uint16_t>(ptr, x0, y0, z0, ystride, zstride);
			} else if(bpv==4) {
				swapBytes<uint32_t>(ptr, x0, y0, z0, ystride, zstride);
			} else if(bpv==8) {
				swapBytes<uint64_t>(ptr, x0, y0, z0, ystride, zstride);
			}
		}

		if(isGzip) {
			if(!fs.pop())
				return false;
		}
		return true;
	}
	template<typename T> void swapBytes(char* pptr, int32_t x0, int32_t y0, int32_t z0, int64_t ystride, int64_t zstride) {
		for(size_t z=0; z<sizes[2]; z++) {
			for(size_t y=0; y<sizes[1]; y++) {
				T* ptr=reinterpret_cast<T*>(pptr+(y0+y)*ystride+(z0+z)*zstride)+x0;
				for(size_t x=0; x<sizes[0]; x++) {
					ptr[x]=swap_bytes<T>(ptr[x]);
				}
			}
		}
	}

	std::string& normalize(size_t start, std::string& str) {
		size_t i=0;
		bool nospace=true;
		for(size_t j=start; j<str.size(); j++) {
			if(isspace(str[j])) {
				if(!nospace)
					str[i++]=' ';
				nospace=true;
			} else {
				str[i++]=str[j];
				nospace=false;
			}
		}
		if(i>0 && str[i-1]==' ')
			i--;
		str.resize(i);
		return str;
	}
	bool checkType(const std::string& str) {
		if(str=="signed char" || str=="int8" || str=="int8_t") {
			imgType=CubeType::I8;
		} else if(str=="uchar" || str=="unsigned char" || str=="uint8" || str=="uint8_t") {
			imgType=CubeType::U8;
		} else if(str=="short" || str=="short int" || str=="signed short" || str=="signed short int" || str=="int16" || str=="int16_t") {
			imgType=CubeType::I16;
		} else if(str=="ushort" || str=="unsigned short" || str=="unsigned short int" || str=="uint16" || str=="uint16_t") {
			imgType=CubeType::U16;
		} else if(str=="int" || str=="signed int" || str=="int32" || str=="int32_t") {
			imgType=CubeType::I32;
		} else if(str=="uint" || str=="unsigned int" || str=="uint32" || str=="uint32_t") {
			imgType=CubeType::U32;
		} else if(str=="longlong" || str=="long long" || str=="long long int" || str=="signed long long" || str=="signed long long int" || str=="int64" || str=="int64_t") {
			imgType=CubeType::I64;
		} else if(str=="ulonglong" || str=="unsigned long long" || str=="unsigned long long int" || str=="uint64" || str=="uint64_t") {
			imgType=CubeType::U64;
		} else if(str=="float") {
			imgType=CubeType::F32;
		} else if(str=="double") {
			imgType=CubeType::F64;
		} else if(str=="block") {
			imgType=CubeType::Unknown;
			return false;
		} else {
			imgType=CubeType::Unknown;
			return false;
		}
		return true;
	}
	bool checkEncoding(const std::string& str) {
		if(str=="gzip") {
			isGzip=true;
		} else if(str=="raw") {
			isGzip=false;
		} else {
			return false;
		}
		return true;
	}
	bool checkDim(const std::string& str) {
		if(str.empty())
			return false;
		char* eptr;
		dim=strtoul(&str[0], &eptr, 10);
		if(eptr!=&str[0]+str.size())
			return false;
		if(dim>0 && dim<=3) {
			return true;
		}
		return false;
	}
	bool checkSizes(const std::string& str) {
		const char* ptr=&str[0];
		const char* end=&str[str.size()];
		size_t i;
		for(i=0; i<dim && i<3 && ptr<end; i++) {
			char* eptr;
			sizes[i]=strtoul(ptr, &eptr, 10);
			if(eptr==ptr)
				return false;
			if(eptr<end) {
				if(*eptr!=' ')
					return false;
				ptr=eptr+1;
			} else {
				ptr=eptr;
			}
		}
		if(i==dim && ptr==end) {
			for(size_t d=dim; d<3; d++)
				sizes[d]=1;
			return true;
		}
		return false;
	}
	bool checkEndian(const std::string& str) {
		if(str=="big") {
			isLE=false;
		} else if(str=="little") {
			isLE=true;
		} else {
			return false;
		}
		return true;
	}
	bool checkHeader() {
		std::string line;
		fnt_getline(fs, line);
		if(!fs)
			return false;
		if(line.compare(0, 4, "NRRD")!=0)
			return false;
		fnt_getline(fs, line);
		while(fs) {
			if(line[0]=='#') {
			} else if(line.compare(0, 5, "type:")==0) {
				if(!checkType(normalize(5, line)))
					return false;
			} else if(line.compare(0, 9, "encoding:")==0) {
				if(!checkEncoding(normalize(9, line)))
					return false;
			} else if(line.compare(0, 10, "dimension:")==0) {
				if(!checkDim(normalize(10, line)))
					return false;
			} else if(line.compare(0, 6, "sizes:")==0) {
				if(!checkSizes(normalize(6, line)))
					return false;
			} else if(line.compare(0, 7, "endian:")==0) {
				if(!checkEndian(normalize(7, line)))
					return false;
			} else if(line.empty()) {
				dataStart=fs.tellg();
				return true;
			} else {
			}
			fnt_getline(fs, line);
		}
		return false;
	}

	public:
	NrrdReader(const std::string& fn, const std::vector<char*>* bufs, size_t size):
		fs{bufs, size}, headerOk{false}, imgType{CubeType::Unknown}, dim{0}, isGzip{false}, isLE{true}
	{
		headerOk=checkHeader();
	}
	NrrdReader(const std::string& fn):
		fs{fn}
	{
		headerOk=checkHeader();
	}
};

extern "C" tsize_t tiff_write_proc(thandle_t fh, tdata_t ptr, tsize_t n) {
	return 0;
}
extern "C" tsize_t tiff_read_proc(thandle_t fh, tdata_t ptr, tsize_t n) {
	auto fs=static_cast<InputStream*>(fh);
	if(fs->read(static_cast<char*>(ptr), n))
		return n;
	return 0;
}
extern "C" toff_t tiff_seek_proc(thandle_t fh, toff_t off, int whence) {
	auto fs=static_cast<InputStream*>(fh);
	switch(whence) {
		case SEEK_SET:
			fs->seekg(off, std::ios_base::beg);
			break;
		case SEEK_CUR:
			fs->seekg(off, std::ios_base::cur);
			break;
		case SEEK_END:
			fs->seekg(off, std::ios_base::end);
			break;
	}
	auto ret=fs->tellg();
	return ret;
}
extern "C" int tiff_close_proc(thandle_t fh) {
	//auto fs=static_cast<InputStream*>(fh);
	return 1;
}
extern "C" toff_t tiff_size_proc(thandle_t fh) {
	auto fs=static_cast<InputStream*>(fh);
	auto pos=fs->tellg();
	fs->seekg(0, std::ios_base::end);
	auto ret=fs->tellg();
	fs->seekg(pos, std::ios_base::beg);
	return ret;
}

template<bool TILED>
inline uint64_t getTileSize(TIFF* tif);
template<>
inline uint64_t getTileSize<true>(TIFF* tif) {
	return TIFFTileSize(tif);
}
template<>
inline uint64_t getTileSize<false>(TIFF* tif) {
	return TIFFStripSize(tif);
}

template<uint16 PC>
inline uint64_t getTileBufSize(uint64_t tileSize, uint16 spp);
template<>
inline uint64_t getTileBufSize<PLANARCONFIG_CONTIG>(uint64_t tileSize, uint16 spp) {
	return tileSize;
}
template<>
inline uint64_t getTileBufSize<PLANARCONFIG_SEPARATE>(uint64_t tileSize, uint16 spp) {
	return tileSize*spp;
}

template<bool TILED>
inline uint32 getYCoord(uint32 j, uint32 h);
template<>
inline uint32 getYCoord<true>(uint32 j, uint32 h) {
	return h-1-j;
}
template<>
inline uint32 getYCoord<false>(uint32 j, uint32 h) {
	return j;
}

template<bool TILED, uint16 PC>
inline void readTile(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y);
template<>
inline void readTile<true, PLANARCONFIG_CONTIG>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	auto tile=TIFFComputeTile(tif, x, y, 0, 0);
	if(-1==TIFFReadEncodedTile(tif, tile, bufTile, tileSize))
		throwError("Failed to read tile.\n");
}
template<>
inline void readTile<false, PLANARCONFIG_CONTIG>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	auto strip=TIFFComputeStrip(tif, y, 0);
	if(-1==TIFFReadEncodedStrip(tif, strip, bufTile, tileSize))
		throwError("Failed to read strip.\n");
}
template<>
inline void readTile<true, PLANARCONFIG_SEPARATE>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	for(tsample_t s=0; s<spp; s++) {
		auto tile=TIFFComputeTile(tif, x, y, 0, s);
		if(-1==TIFFReadEncodedTile(tif, tile, bufTile+s*tileSize, tileSize))
			throwError("Failed to read tile.\n");
	}
}
template<>
inline void readTile<false, PLANARCONFIG_SEPARATE>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	for(tsample_t s=0; s<spp; s++) {
		auto strip=TIFFComputeStrip(tif, y, s);
		if(-1==TIFFReadEncodedStrip(tif, strip, bufTile+s*tileSize, tileSize))
			throwError("Failed to read strip.\n");
	}
}

template<uint16 PC>
inline uint8_t* getBufStart(uint8_t* bufTile, uint64_t tileSize, tsample_t s);
template<>
inline uint8_t* getBufStart<PLANARCONFIG_CONTIG>(uint8_t* bufTile, uint64_t tileSize, tsample_t s) {
	return bufTile;
}
template<>
inline uint8_t* getBufStart<PLANARCONFIG_SEPARATE>(uint8_t* bufTile, uint64_t tileSize, tsample_t s) {
	return bufTile+s*tileSize;
}

template<uint16 PC>
inline uint32 getBufIndex(uint32 idx, uint16 spp, tsample_t s);
template<>
inline uint32 getBufIndex<PLANARCONFIG_CONTIG>(uint32 idx, uint16 spp, tsample_t s) {
	return idx*spp+s;
}
template<>
inline uint32 getBufIndex<PLANARCONFIG_SEPARATE>(uint32 idx, uint16 spp, tsample_t s) {
	return idx;
}

const int conv1bit[]={0, 255};
const int conv2bit[]={0, 85, 170, 255};
const int conv4bit[]={0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255};

template<uint16 BPS, typename T>
inline T getValuePal(uint8_t* bufStart, uint32 bufIdx);
template<>
inline uint8_t getValuePal<1, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return (bufStart[bufIdx/8]>>(7-bufIdx%8))&0x01;
}
template<>
inline uint8_t getValuePal<2, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return (bufStart[bufIdx/4]>>(3-bufIdx%4)*2)&0x03;
}
template<>
inline uint8_t getValuePal<4, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return (bufStart[bufIdx/2]>>(1-bufIdx%2)*4)&0x0F;
}
template<>
inline uint8_t getValuePal<8, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return bufStart[bufIdx];
}
template<>
inline uint16_t getValuePal<16, uint16_t>(uint8_t* bufStart, uint32 bufIdx) {
	uint16* p=reinterpret_cast<uint16*>(bufStart+bufIdx*2);
	return *p;
}

template<uint16 BPS, typename T>
inline T getValue(uint8_t* bufStart, uint32 bufIdx);
template<>
inline uint8_t getValue<1, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return conv1bit[(bufStart[bufIdx/8]>>(7-bufIdx%8))&0x01];
}
template<>
inline uint8_t getValue<2, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return conv2bit[(bufStart[bufIdx/4]>>(3-bufIdx%4)*2)&0x03];
}
template<>
inline uint8_t getValue<4, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return conv4bit[(bufStart[bufIdx/2]>>(1-bufIdx%2)*4)&0x0F];
}
template<>
inline uint8_t getValue<8, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return bufStart[bufIdx];
}
template<>
inline uint16_t getValue<16, uint16_t>(uint8_t* bufStart, uint32 bufIdx) {
	uint16_t* p=reinterpret_cast<uint16_t*>(bufStart+bufIdx*2);
	return *p;
}

template<typename T, typename T0, uint16 BPS, uint16 PC, bool TILED>
inline void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* bufSlice, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	uint16* colors[3];
	if(!TIFFGetField(tif, TIFFTAG_COLORMAP, &colors[0], &colors[1], &colors[2]))
		throwError("Failed to read colormap.\n");
	auto tileSize=getTileSize<TILED>(tif);
	auto tileBufSize=getTileBufSize<PC>(tileSize, spp);
	if(bufTile.size()<tileBufSize)
		bufTile.resize(tileBufSize);
	uint64_t tilenx=((width+tileW-1)/tileW);
	uint64_t tilen=((height+tileH-1)/tileH)*tilenx;
	for(uint64_t tilei=0; tilei<tilen; tilei++) {
		auto y=tilei/tilenx*tileH;
		auto x=tilei%tilenx*tileW;
		readTile<TILED, PC>(tif, bufTile.data(), tileSize, 1, x, y);
		uint32 w=tileW;
		if(w+x>width) {
			w=width-x;
		}
		uint32 h=tileH;
		if(h+y>height) {
			h=height-y;
		}
		for(tsample_t s=0; s<1; s++) {
			uint8_t *bufStart=getBufStart<PC>(bufTile.data(), tileSize, s);
			for(uint32 j=0; j<h; j++) {
				auto bufOut=reinterpret_cast<T*>(bufSlice+x*sizeof(T)+(y+getYCoord<TILED>(j, h))*ystride);
				for(uint32 i=0; i<w; i++) {
					auto bufIdx=getBufIndex<PC>(j*tileW+i, spp, s);
					auto vp=getValuePal<BPS, T0>(bufStart, bufIdx);
					for(tsample_t c=0; c<1; c++) {
						auto v=colors[c][vp];
						T ov;
						ov=v;
						bufOut[i]=ov;
					}
				}
			}
		}
	}
}
template<typename T, typename T0, uint16 BPS, uint16 PC, bool TILED>
inline void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* bufSlice, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	auto tileSize=getTileSize<TILED>(tif);
	auto tileBufSize=getTileBufSize<PC>(tileSize, spp);
	if(bufTile.size()<tileBufSize)
		bufTile.resize(tileBufSize);

	uint64_t tilenx=((width+tileW-1)/tileW);
	uint64_t tilen=((height+tileH-1)/tileH)*tilenx;
	for(uint64_t tilei=0; tilei<tilen; tilei++) {
		auto y=tilei/tilenx*tileH;
		auto x=tilei%tilenx*tileW;
		readTile<TILED, PC>(tif, bufTile.data(), tileSize, 1, x, y);
		uint32 w=tileW;
		if(w+x>width) {
			w=width-x;
		}
		uint32 h=tileH;
		if(h+y>height) {
			h=height-y;
		}
		for(tsample_t s=0; s<1; s++) {
			uint8_t *bufStart=getBufStart<PC>(bufTile.data(), tileSize, s);
			for(uint32 j=0; j<h; j++) {
				auto bufOut=reinterpret_cast<T*>(bufSlice+x*sizeof(T)+(y+getYCoord<TILED>(j, h))*ystride);
				for(uint32 i=0; i<w; i++) {
					auto bufIdx=getBufIndex<PC>(j*tileW+i, spp, s);
					auto v=getValue<BPS, T0>(bufStart, bufIdx);
					T ov;
					ov=v;
					bufOut[i]=ov;
				}
			}
		}
	}
}
template<typename T, typename T0, uint16 BPS, uint16 PC>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	if(tiled) {
		return convertTile<T, T0, BPS, PC, true>(tif, width, height, bufTile, buf, tileW, tileH, spp, ystride);
	} else {
		return convertTile<T, T0, BPS, PC, false>(tif, width, height, bufTile, buf, tileW, tileH, spp, ystride);
	}
}
template<typename T, typename T0, uint16 BPS, uint16 PC>
void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	if(tiled) {
		return convertTilePal<T, T0, BPS, PC, true>(tif, width, height, bufTile, buf, tileW, tileH, spp, ystride);
	} else {
		return convertTilePal<T, T0, BPS, PC, false>(tif, width, height, bufTile, buf, tileW, tileH, spp, ystride);
	}
}
template<typename T, typename T0, uint16 BPS>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	switch(pc) {
		case PLANARCONFIG_CONTIG:
			return convertTile<T, T0, BPS, PLANARCONFIG_CONTIG>(tif, width, height, bufTile, buf, tiled, tileW, tileH, spp, ystride);
		case PLANARCONFIG_SEPARATE:
			return convertTile<T, T0, BPS, PLANARCONFIG_SEPARATE>(tif, width, height, bufTile, buf, tiled, tileW, tileH, spp, ystride);
		default:
			throwError("Planar configuration ", pc, " not supported.\n");
	}
}
template<typename T, typename T0, uint16 BPS>
void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	switch(pc) {
		case PLANARCONFIG_CONTIG:
			return convertTilePal<T, T0, BPS, PLANARCONFIG_CONTIG>(tif, width, height, bufTile, buf, tiled, tileW, tileH, spp, ystride);
		case PLANARCONFIG_SEPARATE:
			return convertTilePal<T, T0, BPS, PLANARCONFIG_SEPARATE>(tif, width, height, bufTile, buf, tiled, tileW, tileH, spp, ystride);
		default:
			throwError("Planar configuration ", pc, " not supported.\n");
	}
}
template<typename T>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride);
template<>
void convertTile<uint8_t>(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	switch(bps) {
		case 1:
			return convertTile<uint8_t, uint8_t, 1>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 2:
			return convertTile<uint8_t, uint8_t, 2>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 4:
			return convertTile<uint8_t, uint8_t, 4>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 8:
			return convertTile<uint8_t, uint8_t, 8>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 16:
			return convertTile<uint8_t, uint16_t, 16>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		default:
			throwError("Bits per sample ", bps, " not supported.\n");
	}
}
template<>
void convertTile<uint16_t>(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	switch(bps) {
		case 16:
			return convertTile<uint16_t, uint16_t, 16>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		default:
			throwError("Bits per sample ", bps, " not supported.\n");
	}
}
template<typename T>
void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp, int64_t ystride) {
	switch(bps) {
		case 1:
			return convertTilePal<T, uint8_t, 1>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 2:
			return convertTilePal<T, uint8_t, 2>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 4:
			return convertTilePal<T, uint8_t, 4>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 8:
			return convertTilePal<T, uint8_t, 8>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		case 16:
			return convertTilePal<T, uint16_t, 16>(tif, width, height, bufTile, buf, pc, tiled, tileW, tileH, spp, ystride);
		default:
			throwError("Bits per sample ", bps, " not supported.\n");
	}
}
template<typename T>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, char* buf, int64_t ystride) {
	uint16 pm;
	if(!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &pm))
		throwError("Failed to read PHOTOMETRIC.\n");
	uint16 bps;
	if(!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps))
		throwError("Failed to read BITSPERSAMPLE.\n");
	uint16 pc;
	if(!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &pc))
		throwError("Failed to read PLANARCONFIG.\n");
	bool tiled=TIFFIsTiled(tif);

	uint32 tileW, tileH;
	if(tiled) {
		if(!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileW)
				|| !TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileH))
			throwError("Failed to get tile width/length information.\n");
	} else { // A strip is like a tile.
		tileW=width;
		if(!TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &tileH))
			throwError("Failed to get rows/strip information.");
	}
	uint16 spp;
	if(!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp))
		throwError("Failed to read SAMPLESPERPIXEL.\n");

	switch(pm) {
		case PHOTOMETRIC_MINISWHITE:
		case PHOTOMETRIC_MINISBLACK:
		case PHOTOMETRIC_SEPARATED:
		case PHOTOMETRIC_RGB:
			return convertTile<T>(tif, width, height, bufTile, buf, bps, pc, tiled, tileW, tileH, spp, ystride);
		case PHOTOMETRIC_PALETTE:
			return convertTilePal<T>(tif, width, height, bufTile, buf, bps, pc, tiled, tileW, tileH, spp, ystride);
		default:
			// Not supporting colormap
			throwError("Photometric type ", pm, " not supported.\n");
	}
}


class TiffReader: public ImageReader {
	InputStream fs;
	TIFF* tif;
	CubeType imgType;
	size_t w, h, d;
	bool close() {
		if (tif)
			TIFFClose(tif);
		fs.close();
		return true;
	}
	~TiffReader() {
		if(tif)
			TIFFClose(tif);
		fs.close();
	}
	bool getType(CubeType* type) override {
		if(!tif)
			return false;
		*type=imgType;
		return true;
	}
	bool getSizes(int64_t* pw, int64_t* ph, int64_t* pd) override {
		if(!tif)
			return false;
		*pw=w;
		*ph=h;
		if(!d) {
			if(!TIFFSetDirectory(tif, 0))
				return false;
			do {
				d++;
			} while(TIFFReadDirectory(tif));
		}
		*pd=d;
		return true;
	}
	template<typename T> bool readTiffData(char* ptr, int64_t ystride, int64_t zstride) {
		std::vector<uint8_t> bufTile;
		if(!TIFFSetDirectory(tif, 0))
			return false;
		try {
			int32_t z=0;
			do {
				convertTile<T>(tif, w, h, bufTile, ptr+z*zstride, ystride);
				z++;
			} while(TIFFReadDirectory(tif));
		} catch(...) {
			return false;
		}
		return true;
	}

	bool readData(char* ptr, int32_t x0, int32_t y0, int32_t z0, int64_t ystride, int64_t zstride) override {
		if(!tif)
			return false;
		switch(imgType) {
			case CubeType::U8:
				return readTiffData<uint8_t>(ptr+x0*1+y0*ystride+z0*zstride, ystride, zstride);
			case CubeType::U16:
				return readTiffData<uint16_t>(ptr+x0*2+y0*ystride+z0*zstride, ystride, zstride);
			default:
				return false;
		}
	}

	void checkType() {
		if(!tif)
			return;

		if(!TIFFSetDirectory(tif, 0)) {
			TIFFClose(tif);
			tif=nullptr;
			return;
		}

		uint16 spp, pm, pc, bps;
		uint32 width, height;
		try {
			if(!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width)
					|| !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height))
				throwError("No width/length information in file: ");
			if(width==0 || height==0)
				throwError("Zero width/length: ");
			if(!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp))
				throwError("Failed to read SAMPLESPERPIXEL.");
			if(spp==0)
				throwError("Zero sample per pixel");
			if(!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &pm))
				throwError("Failed to read PHOTOMETRIC.");
			switch(pm) {
				case PHOTOMETRIC_MINISWHITE:
				case PHOTOMETRIC_MINISBLACK:
				case PHOTOMETRIC_SEPARATED:
					break;
				case PHOTOMETRIC_RGB:
					if(spp!=3)
						throwError("Wrong spp for RGB image");
					break;
				case PHOTOMETRIC_PALETTE:
					if(spp!=1)
						throwError("Wrong spp for PALETTE image");
					break;
				default:
					throwError("Unsupported photometric");
			}
			if(!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &pc))
				throwError("Failed to read PLANARCONFIG.");
			switch(pc) {
				case PLANARCONFIG_CONTIG:
				case PLANARCONFIG_SEPARATE:
					break;
				default:
					throwError("Unsupported planar config");
			}
			if(!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps))
				throwError("Failed to read BITSPERSAMPLE.");
			switch(bps) {
				case 1:
				case 2:
				case 4:
				case 8:
					imgType=CubeType::U8;
					break;
				case 16:
					imgType=CubeType::U16;
					break;
				default:
					throwError("Unsupported bits per sample");
			}
			if(pm==PHOTOMETRIC_PALETTE)
				imgType=CubeType::U16;
			w=width;
			h=height;
		} catch(...) {
			TIFFClose(tif);
			tif=nullptr;
			return;
		}
	}

	public:
	TiffReader(const std::string& fn, const std::vector<char*>* bufs, size_t size):
		fs{bufs, size}, tif{nullptr}, imgType{CubeType::Unknown}, w{0}, h{0}, d{0}
	{
		 TIFFSetWarningHandler(nullptr);
		tif=TIFFClientOpen(fn.c_str(), "r", &fs, tiff_read_proc, tiff_write_proc, tiff_seek_proc, tiff_close_proc, tiff_size_proc, nullptr, nullptr);
		checkType();
	}
	TiffReader(const std::string& fn):
		fs{fn}, tif{nullptr}, imgType{CubeType::Unknown}, w{0}, h{0}, d{0}
	{
		 TIFFSetWarningHandler(nullptr);
		tif=TIFFClientOpen(fn.c_str(), "r", &fs, tiff_read_proc, tiff_write_proc, tiff_seek_proc, tiff_close_proc, tiff_size_proc, nullptr, nullptr);
		checkType();
	}
};

#ifdef WITH_VP9
#include "image.reader.webm.cc"
#endif

static bool endsWith(const std::string& str, const std::string& type) {
	auto l1=str.length();
	auto l2=type.length();
	if(l1<l2)
		return false;
	auto dl=l1-l2;
	for(size_t i=0; i<l2; i++) {
		if(toupper(str[i+dl])!=toupper(type[i]))
			return false;
	}
	return true;
}

ImageReader* ImageReader::create(const std::string& fn, const std::vector<char*>* bufs, size_t size) {
	if(endsWith(fn, ".nrrd"))
		return new NrrdReader{fn, bufs, size};
	if(endsWith(fn, ".tiff"))
		return new TiffReader{fn, bufs, size};
	if(endsWith(fn, ".tif"))
		return new TiffReader{fn, bufs, size};
#ifdef WITH_VP9
	if(endsWith(fn, ".webm"))
		return new WebmReader{fn, bufs, size};
#endif
#ifdef WITH_HEVC
	if(endsWith(fn, ".hevc"))
		return new HevcReader{fn, bufs, size};
#endif
	std::cerr<<"Not supported\n";
	return nullptr;
}
ImageReader* ImageReader::create(const std::string& fn) {
	if(endsWith(fn, ".nrrd"))
		return new NrrdReader{fn};
	if(endsWith(fn, ".tiff"))
		return new TiffReader{fn};
	if(endsWith(fn, ".tif"))
		return new TiffReader{fn};
#ifdef WITH_VP9
	if(endsWith(fn, ".webm"))
		return new WebmReader{fn};
#endif
#ifdef WITH_HEVC
	if(endsWith(fn, ".hevc"))
		return new HevcReader{fn};
#endif
	std::cerr<<"Not supported\n";
	return nullptr;
}



#ifdef WITH_HEVC
#include "image.reader.hevc.cc"
#endif
